Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Easier specification of external file dependencies #83

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

ramnathv
Copy link
Owner

This PR makes it easy for package developers and users to specify file dependencies for their widgets. Here is an example:

# install_github('htmlwidgets/datamaps')
library(datamaps)
datamaps(
  scope = 'pcs',
  geographyConfig = list(dataUrl = htmlwidgets:::attachment("data/pcs.json"))
)

It uses a similar mechanism to the one used by the JS function to mark objects as javascript literals. The resolution of attachment urls is carried out using the HTMLWidgets.getAttachmentUrl function.

@jcheng5 can you take a look at this whenever you have time. @jjallaire @timelyportfolio @yihui if you can test this functionality with any of your widgets and provide me with feedback/comments, that would be very useful.

#' Mark a string as an attachment
#' @export
attachment <- function(x){
structure(normalizePath(x), class = unique(c("ATTACHMENT", oldClass(x))))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why oldClass vs. class here?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just used the same code as used in htmlwidgets:::JS. I should admit I did not understand why oldClass was used there in the first place 😄

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should also probably add a file.exists(x) check in the attachment function to ensure that incorrect paths are caught early on.

#' @export
attachment <- function(x){
if (!file.exists(x)){
stop("The attachment ", x, " does not exist")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about just leaving it to normalizePath(mustWork = TRUE)?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion! I will make the change.

@nsh87
Copy link

nsh87 commented May 28, 2015

This would be a most-welcome feature. I have been looking at this issue in my attempt to attach a file dependency that gets created in one of my functions before htmlwidgets::createWidget. Thus far I have not been successful in making the file accessible through the browser.

@jcheng5
Copy link
Collaborator

jcheng5 commented May 28, 2015

@nsh87 Does this data need to be packaged as a separate file? Could it perhaps be included as part of the createWidget's x value?

@nsh87
Copy link

nsh87 commented May 28, 2015

@jcheng5 It's an XML file, so I don't think it can be included with x since that goes through toJSON. Outside of htmlwidgets, the JS library I'm using reads the data from an XML file, typically through AJAX. What I'm attempting to do is to get the file into <head> as an attachment and try to read it from there, but no luck attaching it so far.

radial_phylo <- function(user-data, width=NULL, height=NULL) {

   # Do some stuff to save an XML file to xmlFilePath

   phyloxml <- htmltools::htmlDependency(
     name = 'phyloxmls',
     version = '1.0',
     src = c(file=xmlFilePath)
     attachment = list(xml = xmlFilePath)  # Pretty sure this isn't right
   )

  # create widget
  htmlwidgets::createWidget(
    name = 'radial_phylo',
    x,
    width = width,
    height = height,
    htmlwidgets::sizingPolicy(
      viewer.padding = 0,
      viewer.suppress = suppressViewer,
      browser.fill = TRUE
    ),
    package = 'receptormarker',
    dependencies = phyloxml
  )
}

@jcheng5
Copy link
Collaborator

jcheng5 commented May 28, 2015

I think that's pretty close--in the JavaScript side you should then be able to call HTMLWidgets.getAttachmentUrl("phyloxmls", "xml") and get back a URL...?

@nsh87
Copy link

nsh87 commented May 28, 2015

That JS call works, but I'm not sure if the reference is right. If I inspect the attachment in the HTML and click on the link it's just a blank XML file (and xmlFilePath is not blank).

var phyloxmlfile = HTMLWidgets.getAttachmentUrl('phyloxmls', 'xml');
// phyloxmlfile = "lib/phyloxml-1.0//Users/Nikhil/dev/receptormarker/receptormarker/phyloxml-69647efd9cfd.xml"

@nsh87
Copy link

nsh87 commented May 28, 2015

Plus I don't have a "inst/htmlwidgets/lib/phyloxml-1.0" folder. Judging by the fact that it's looking in "lib/phyloxml-1.0", it looks like it's expecting the XML asset to exist in my package, but the XML file is user-generated.

@jcheng5
Copy link
Collaborator

jcheng5 commented May 28, 2015

This chunk:

   phyloxml <- htmltools::htmlDependency(
     name = 'phyloxmls',
     version = '1.0',
     src = c(file=xmlFilePath)
     attachment = list(xml = xmlFilePath)  # Pretty sure this isn't right
   )

Should be this:

   phyloxml <- htmltools::htmlDependency(
     name = 'phyloxmls',
     version = '1.0',
     src = c(file=dirname(xmlFilePath))
     attachment = list(xml = basename(xmlFilePath))  # Pretty sure this isn't right
   )

Note that the entire contents of dirname(xmlFilePath) will be copied along with xmlFilePath, so if that includes a lot of extraneous stuff, copy your XML file to an empty temp directory and then pass that temp directory as your src (and still the basename() as the attachment value).

@nsh87
Copy link

nsh87 commented May 28, 2015

Awesome, that did the trick. Lines 7 and 8 in "R/fileDependency.R" at the top of this pull request should've been good hints for me. Thanks.

@jcheng5
Copy link
Collaborator

jcheng5 commented May 28, 2015

OK, great, glad to hear it. :)

@ramnathv
Copy link
Owner Author

@jcheng5 Should we revisit this PR (i need some cleanup so it is mergeable)? All this PR does is provide a wrapper around the dependency API that you have put in. So maybe one way to go about this is to merge this in, acknowledging the limitations you have pointed out, and then once we have a more complete way of doing this, update the mechanism. Let me know what you think.

@timelyportfolio
Copy link
Collaborator

👍 +1

@jcheng5
Copy link
Collaborator

jcheng5 commented May 28, 2015

I think it might be a simple tweak to get it working for dynamic values. Give me a minute...

@ramnathv
Copy link
Owner Author

@jcheng5 I look forward to your magic 👍

Without this change, the attachment() feature might return stale
data, since it identified file dependencies only by basename.
@jcheng5
Copy link
Collaborator

jcheng5 commented May 28, 2015

OK, I pushed some changes. Some notes:

  1. I took dependencies on digest and bitops, hope that's OK.
  2. Should fileDependency be exported? I'm not sure how one would use it correctly outside of attachment() (although perhaps that's only the case after my change).
  3. The underlying mechanism for traversing the data object based on key, will only work if neither the attachments nor their parent lists are ever unnamed lists (nor may their names have periods in them).
  4. I didn't write docs/examples, that would need to be done too before merging.

@ramnathv
Copy link
Owner Author

Thanks @jcheng5. I will play with your PR and do some testing. I am okay with the dependencies on digest and bitops. I will be happy to write up docs/examples before we merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants